package hibernatemock;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;

import lombok.Getter;
import lombok.Setter;

public class SetupBuilder {

	@Setter
	@Getter
	private List<Setup> setups;
	@Setter
	private InstanceFactory instanceFactory;

	public SetupBuilder when(Class<?>... classes) {
		List<Class<?>> notAnnotations = new ArrayList<Class<?>>();
		List<Class<?>> annotations = new ArrayList<Class<?>>();
		for (Class<?> classTemp : classes) {
			if (Annotation.class.isAssignableFrom(classTemp))
				annotations.add(classTemp);
			else
				notAnnotations.add(classTemp);
		}

		if (annotations.size() > 1)
			throw new RuntimeException("You can setup only one annotation at a time.");

		for (Class<?> noAnnotationClass : notAnnotations) {
			if (annotations.size() == 0) {
				Setup setup = new Setup(noAnnotationClass);

				int index = setups.indexOf(setup);
				if (index != -1) {
					setups.set(index, setup);
				} else {
					setups.add(setup);
				}
			}
			for (Class<?> annotationClass : annotations) {
				Setup setup = new Setup(noAnnotationClass, annotationClass);
				int index = setups.indexOf(setup);
				if (index != -1) {
					setups.set(index, setup);
				} else {
					setups.add(setup);
				}
			}
		}
		return this;
	}

	public void deliver(Object instance) {
		realDeliver(instance);
	}

	public void deliverWithVarArgs(Object... fieldsAndValues) {
		List<Setup> setupsNoDelivery = setupsNoDelivery();
		for (Setup setup : setupsNoDelivery) {
			Object instance = instanceFactory.newInstance(setup.getClazzField());
			for (int i = 0; i < fieldsAndValues.length; i = i + 2) {
				try {
					Field field = setup.getClazzField().getDeclaredField(fieldsAndValues[i].toString());
					field.setAccessible(true);
					field.set(instance, fieldsAndValues[i + 1]);
				} catch (Exception e) {
					throw new RuntimeException(e);
				}
			}
			setup.addDelivery();
			setup.setInstance(instance);
		}

	}

	private List<Setup> setupsNoDelivery() {
		List<Setup> setupsTemp = new ArrayList<Setup>();
		for (Setup setup : setups)
			if (setup.getDeliveryCount() == 0)
				setupsTemp.add(setup);
		if (setupsTemp.size() == 0) {
			Setup last = setups.get(setups.size() - 1);
			Setup setup1 = new Setup();
			setup1.setClazzAnnotation(last.getClazzAnnotation());
			setup1.setClazzField(last.getClazzField());
			setupsTemp.add(setup1);
			setups.add(setup1);
		}
		return setupsTemp;
	}

	public void deliver(FieldHandler fieldHandler) {
		realDeliver(fieldHandler);
	}

	private void realDeliver(Object instance) {
		for (Setup setup : setupsNoDelivery()) {
			setup.addDelivery();
			setup.setInstance(instance);
		}
	}

}
